package com.google.code.tretris.server;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.google.code.tretris.R;
import com.google.code.tretris.ServerConsoleActivity;
import com.google.code.tretris.common.WKC;
import com.google.code.tretris.events.IPCClient;
import com.google.code.tretris.events.MoveEvent;
import com.google.code.tretris.events.UpdateBoardEvent;
import com.google.code.tretris.network.NetworkTNG;
import com.google.code.tretris.network.exceptions.NotConnectedException;
import com.google.code.tretris.network.impl.NetworkManager;

public class GamePlayService extends Service implements HeartbeatListener {
	private static final String LOG_TAG = GamePlayService.class.getSimpleName();
	private NetworkTNG networkManager;
	private Timer timer;
	private boolean playInProgress = false;
	private boolean started = false;
	private final HashMap<String, GamePlayer> gamePlayers = new HashMap<String, GamePlayer>();
	private final RemoteCallbackList<IPCClient> clients = new RemoteCallbackList<IPCClient>();

	private TretrisServer.Stub idlStub = new TretrisServer.Stub() {

		public boolean isPlayInProgress() throws RemoteException {
			return GamePlayService.this.isPlayInProgress();
		}

		public boolean isStarted() throws RemoteException {
			return GamePlayService.this.isStarted();
		}

		public boolean LogIntoNetworkAs(String username, String password)
				throws RemoteException {
			try {
				networkManager.connect(username, password);
			} catch (NotConnectedException e) {
				Log.e(LOG_TAG, "Unable to connect to the network", e);
			}
			return isLoggedIntoNetwork();
		}

		public boolean invitePlayer(String username) throws RemoteException {
			if (!gamePlayers.containsKey(username)) {
				if (networkManager.invitePlayer(username)) {
					gamePlayers.put(username, new GamePlayer(username));
					return true;
				}
			}
			return false;
		}

		public boolean isLoggedIntoNetwork() throws RemoteException {
			return GamePlayService.this.isLoggedInToNetwork();
		}

		public boolean startPlay() throws RemoteException {
			ArrayList<GamePlayer> players = new ArrayList<GamePlayer>();
			Collection<GamePlayer> values = gamePlayers.values();
			for (GamePlayer player : values) {
				players.add(player);
			}
			final GamePlay gp = new GamePlay(players);
			gp.addHeartbeatListener(GamePlayService.this);
			TimerTask task = new TimerTask() {
				@Override
				public void run() {
					gp.heartbeat();
				}
			};
			setPlayInProgress(true);
			timer.schedule(task, 300, WKC.HEARTBEAT_SLEEP_TIME);

			return true;
		}

		public int getNumberOfPlayers() throws RemoteException {
			return gamePlayers.size();
		}

		public void registerClient(int clientUID, IPCClient client)
				throws RemoteException {
			// Check if the player already has a board.
			final String playerID = Integer.toString(clientUID);
			if (!gamePlayers.containsKey(playerID)) {
				Log.d(LOG_TAG, "Add client:" + clientUID);
				GamePlayer localPlayer = new GamePlayer("IPCClient " + playerID);
				gamePlayers.put(playerID, localPlayer);
			}
			// Register the callback
			clients.register(client);
		}

		public void unregisterClient(int clientUID, IPCClient client)
				throws RemoteException {
			clients.unregister(client);
		}
		
		public void sendMovement(MoveEvent moveEvent) {
			// should probably use Gameplay.pushEvent
			GamePlayer gamePlayer = gamePlayers.get(moveEvent.getUserId());
			gamePlayer.pushEvent(moveEvent);
		}
	};

	public GamePlayService() {
		super();
	}

	@Override
	public IBinder onBind(Intent intent) {
		return idlStub;
	}

	@Override
	public void onCreate() {
		Log.d(LOG_TAG, "onCreate");
		timer = new Timer("Tretris Gameplay");
		networkManager = new NetworkManager();
		super.onCreate();
	}

	@Override
	public void onDestroy() {
		Log.d(LOG_TAG, "onDestroy");
		stopGamePlay();
		networkManager.disconnect();
		networkManager = null;
		NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.cancelAll();

		super.onDestroy();
	}

	private void stopGamePlay() {
		if (isPlayInProgress()) {
			timer.cancel();
			setPlayInProgress(false);
		}
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.d(LOG_TAG, "Gameplay service starting from Intent: "
				+ ((intent != null) ? intent.toUri(0) : "null"));
		setStarted(true);

		NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		Notification notification = new Notification(
				R.drawable.serviceconsolestatusbar, "Started",
				System.currentTimeMillis());
		notification.flags = Notification.FLAG_ONGOING_EVENT
				| Notification.FLAG_NO_CLEAR;
		PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
				new Intent(this, ServerConsoleActivity.class),
				PendingIntent.FLAG_UPDATE_CURRENT);

		Resources resources = getResources();
		String title = resources
				.getString(R.string.notification_title_server_started);
		String text = resources
				.getString(R.string.notification_text_server_started);
		notification.setLatestEventInfo(this, title, text, pendingIntent);

		notificationManager.notify(1, notification);

		return Service.START_STICKY;
	}

	public synchronized boolean isPlayInProgress() {
		return playInProgress;
	}

	public synchronized void setPlayInProgress(boolean playInProgress) {
		this.playInProgress = playInProgress;
	}

	public synchronized boolean isStarted() {
		return started;
	}

	public synchronized void setStarted(boolean started) {
		this.started = started;
	}

	public boolean isLoggedInToNetwork() {
		return networkManager.isConnected();
	}

	public void gameOver() {
		stopGamePlay();
	}

	public void heartbeatResult(final ArrayList<UpdateBoardEvent> result) {
		// send to local clients if any
		int i = clients.beginBroadcast();
		Log.d(LOG_TAG, "Number of local clients: " + i);
		while (i > 0) {
			i--;
			try {
				Log.d(LOG_TAG, "Sending heartbeat result to IPCCLient: " + i);
				clients.getBroadcastItem(i).update(result);
			} catch (RemoteException e) {
				// The RemoteCallbackList will take care of removing
				// the dead object for us.
				Log.e(LOG_TAG, "Remote exception when calling IPC client", e);
			}
		}
		clients.finishBroadcast();

		// Send to network if any
		if (isLoggedInToNetwork()) {
			Log.d(LOG_TAG,
					"Logged in to network, will broadcast heratbeat result");
			try {
				networkManager.broadcastUpdate(result);
			} catch (NotConnectedException e) {
				Log.e(LOG_TAG, "Unable to broadcast game event update", e);
			}
		}
	}

}
